﻿cbuffer cbPerObject
{
	float4x4 WorldViewProjection;	
	float4x4 WorldView;	
	float4x4 View;	
	float4x4 InverseProjection;
	float4x4 ViewToLightProjection;
	float4x4 ViewToLightView;
}

cbuffer cbGlobal
{
	int2 gBufferSize;
	float zFar;
	float4 light;
}

SamplerState GBufferSampler
{
	Filter = MIN_MAG_POINT_MIP_LINEAR;
    AddressU = Clamp;
    AddressV = Clamp;
};
SamplerState ShadowDepthSampler
{
	Filter = MIN_MAG_MIP_LINEAR;//MIN_MAG_MIP_POINT;
	//MaxAnisotropy = 0;
    AddressU = Clamp;
    AddressV = Clamp;
	//ComparisonFunc = GREATER;
	//ComparisonFilter = COMPARISON_MIN_MAG_LINEAR_MIP_POINT;
};

Texture2D albedoTexture;
Texture2D normalTexture;
Texture2D depthTexture;
Texture2D shadowDepthTexture;

struct App2VS
{
	float3 pos : POSITION;
	float3 col : COLOR;
	float3 normal : NORMAL;
};
struct VS2PS
{
	float4 pos : SV_Position;
	float3 viewRay : TEXCOORD0;
};
struct PS2GPU
{
	float4 value : SV_Target0;
};

void vs(App2VS IN, out VS2PS OUT)
{
	OUT.pos = float4(IN.pos, 1); //pass through for quad/etc
	//get the viewspace viewray
	float4 viewPos = mul(float4(IN.pos.xy, 1, 1), InverseProjection);
	OUT.viewRay = viewPos.xyz / viewPos.w;
}
float isNotShadowed(float3 viewPos, float bias, out float debug, out float2 uv)
{
	float4 lightProjPos = mul(float4(viewPos, 1), ViewToLightProjection);
	float2 shadowUv = (lightProjPos.xy /lightProjPos.w);
	shadowUv = float2(shadowUv.x, - shadowUv.y) * 0.5 + 0.5;
	
	float pixDepthInLightProj = (lightProjPos.z) / zFar;
	//begin PCF
	/*
	{
		uv = shadowUv;
		debug = 0;
		float closerCount = 0;
		[unroll]
		for(int du = -1; du < 1; du++)
		{
			[unroll]
			for(int dv = -1; dv < 1; dv++)
			{				
				float occluderDepth = shadowDepthTexture.Sample(ShadowDepthSampler, shadowUv + float2(du * 0.002, dv * 0.002));	
				closerCount += occluderDepth > (pixDepthInLightProj - bias);
			}
		}
		return closerCount / 9;
	}*/
	//begin VSM
	
	{
		uv = floor(shadowUv * 512);

	
		float expConst = 12;
		float pixDepthInLightProj = exp(expConst * lightProjPos.z / zFar);

	

		float2 moments = shadowDepthTexture.SampleBias(ShadowDepthSampler, shadowUv, 1);

		float isLit = moments.x > (pixDepthInLightProj - 0.01f);
		//isLit = 0;

		float ex_2 = moments.x * moments.x;
		float e_x2 = moments.y;
		//float vsmBias = 0.00001f;
		float variance = saturate(e_x2 - ex_2);
		//variance = max(variance, pow(expConst*exp(expConst*0.00000001), 2) );
		variance = max(variance, 1000);
		float m_d = moments.x - pixDepthInLightProj;
		//float m_d = (moments.x - pixDepthInLightProj);
		float p = variance / (variance + m_d * m_d);
	

		debug = isLit;

		float outOfBounds = (shadowUv.x < 0 || shadowUv.x > 1 || shadowUv.y < 0 || shadowUv.y > 1);
		//return saturate((occluderDepth < (pixDepthInLightProj - bias)) + outOfBounds);
		return max(p, isLit) - outOfBounds;
	}
	
	//end VSM

}
void ps(VS2PS IN, out PS2GPU OUT)
{
	float2 uv = IN.pos / gBufferSize;

	//float2 encodedNormal = normalTexture.Sample(GBufferSampler, uv).rg;
	//float3 normal = normalize(decodeNormal(encodedNormal));
	float3 normal = normalize(normalTexture.Sample(GBufferSampler, uv).rgb);
	//linear depth in 0->1 range
	float depth = depthTexture.Sample(GBufferSampler, uv).r;

	float3 albedo = albedoTexture.Sample(GBufferSampler, uv).rgb;
	
	//reconstruct world pos using depth
	float3 viewPos = IN.viewRay * depth;
	float totalRadiance = 0;
	
	float3 lightPos =  light.xyz;
	float lightIntensity = light.w;
	float lightAttenuation = min(1 / pow(distance(lightPos, viewPos), 2), 1) ;
	float lightFactor = lightAttenuation * lightIntensity * dot(normal, normalize(lightPos - viewPos));
	totalRadiance += lightFactor;
	
	float debug;
	float2 shadowUv;
	float isLit = isNotShadowed(viewPos, 0.01f, debug, shadowUv);
	
	OUT.value = float4(isLit * totalRadiance * albedo, 1);
	//OUT.value = float4(1,1,1,1) == OUT.value;
	//OUT.value = shadowUv.x % 2 + shadowUv.y % 2;
	//OUT.value = totalRadiance;
}
BlendState FinalBlend
{ 
    BlendEnable[0] = True;
	SrcBlend = One;
	DestBlend = One;
	BlendOp = Add;
	SrcBlendAlpha = One;
	DestBlendAlpha = One;
	BlendOpAlpha = ADD;
};
technique10 Render
{
	pass P0
	{
		SetGeometryShader( 0 );
		SetVertexShader( CompileShader( vs_4_0, vs() ) );
		SetPixelShader( CompileShader( ps_4_0, ps() ) );
		SetBlendState(FinalBlend, float4(0,0,0,0), 0xf);
	}
}